/**
 * Author xiaol
 * Rev
 * Date: 2021-04-16 21:34
 * <p>
 * Copyright (C) 2021 Seeyon, Inc. All rights reserved.
 * <p>
 * This software is the proprietary information of Seeyon, Inc.
 * Use is subject to license terms.
 * @company seeyon.com
 * @author xiaol
 */
package com.lagou.mvc;

import com.lagou.annotation.RequestMapping;
import com.lagou.annotation.RestController;
import com.lagou.annotation.Security;
import org.springframework.util.CollectionUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;

/**
 * @description
 * @date 2021-04-16 21:34
 * @author xiaol
 */
public class DispatchServlet extends HttpServlet{

	private Properties properties = new Properties();

	private List<String> classPath = new ArrayList<String>();

	private Map<String,Object> beans = new ConcurrentHashMap<String,Object>();

	private List<Class> handlerClass = new ArrayList<>();
	/**
	 * handler映射
	 */
	private Map<String,MethodHandler> handlerMapping = new ConcurrentHashMap<>();

	@Override
	public void init(ServletConfig config) throws ServletException{
		//读取springMvc配置
		doConfig(config);
		//扫描包路径
		doScan();
		//初始化bean
		doBean();
		//绑定mapping映射关系
		doHandlerMapping();
	}

	/**
	 * 绑定mapping映射关系
	 */
	private void doHandlerMapping() {
		handlerClass.forEach(clazz->{
			RequestMapping annotation = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
			String value = annotation.value();
			Method[] declaredMethods = clazz.getDeclaredMethods();
			for (int i = 0; i < declaredMethods.length; i++) {
				Method declaredMethod = declaredMethods[i];
				boolean annotationPresent = declaredMethod.isAnnotationPresent(RequestMapping.class);
				if(annotationPresent){
					RequestMapping annotation1 = declaredMethod.getAnnotation(RequestMapping.class);
					String value1 = annotation1.value();
					MethodHandler handler = new MethodHandler();
					handler.setBeanName(clazz.getSimpleName());
					handler.setMethodName(declaredMethod.getName());
					if(declaredMethod.isAnnotationPresent(Security.class)){
						Security annotation2 = declaredMethod.getAnnotation(Security.class);
						String[] value2 = annotation2.value();
						handler.setAuthName(Arrays.asList(value2));
					}
					String method = annotation1.Method();
					if("GET".equals(method)) {
						Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
						if (parameterTypes.length > 0) {
							Object[] args = new Object[parameterTypes.length];
							for (int i1 = 0; i1 < parameterTypes.length; i1++) {
								Class<?> parameterType = parameterTypes[i1];
								//只适配GET请求参数
								args[i1] = parameterType.getSimpleName();
							}
							handler.setArgs(args);
						}
					}else if("POST".equals(method)){}
					handlerMapping.put(value + value1,handler);
				}
			}
		});
	}

	/**
	 * 初始化bean
	 */
	private void doBean() {
		if(!CollectionUtils.isEmpty(classPath)){
			classPath.forEach(path -> {
				try {
					Class<?> aClass = Class.forName(path);
					if(aClass.isAnnotationPresent(RestController.class)) {
						Object obj = aClass.newInstance();
						handlerClass.add(aClass);
						beans.put(aClass.getSimpleName(),obj);
					}
				} catch (Exception e) {}
			});
		}
	}

	/**
	 * 读取配置文件
	 * @param config
	 */
	private void doConfig(ServletConfig config) {
		String mvcConfig = config.getInitParameter("mvcConfig");
		ClassLoader classLoader = DispatchServlet.class.getClassLoader();
		String classpath = classLoader.getResource("/").getPath();
		try {
			properties.load(classLoader.getResourceAsStream(classpath + File.separator + mvcConfig));
		} catch (IOException e) {}
	}

	/**
	 * 包路径扫描
	 */
	private void doScan() {
		scan(properties.get("mvc.scan"));
	}


	private void scan(Object scanPath) {
		try {
			File file = new File(scanPath.toString());
			File[] files = file.listFiles();
			for (File file1 : files) {
				if (file1.isDirectory()) {
					scan(file1.getPath());
				} else {
					String canonicalPath = file1.getCanonicalPath();
					String clazz = canonicalPath.split("classes\\\\")[1].split(".class")[0].replaceAll(Matcher.quoteReplacement(File.separator), "\\.");
					classPath.add(clazz);
				}
			}
		}catch (Exception e){}
	}

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doPost(req,resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
		String servletPath = req.getServletPath();
		MethodHandler handler = handlerMapping.get(servletPath);
		String beanName = handler.getBeanName();
		String methodName = handler.getMethodName();
		Object[] args = handler.getArgs();
		Object bean = beans.get(beanName);
		try {
			Method method = bean.getClass().getMethod(methodName);
			Map parameterMap = req.getParameterMap();
			List<String> authName = handler.getAuthName();
			Object username = parameterMap.get("username");
			if(authName != null && authName.contains(username)){
				//需要根据userame进行权限判断
				throw new Exception("没有访问权限");
			}
			Object[] params = new Object[args.length];
			if(args.length > 0){
				for (int i = 0; i < args.length; i++) {
					Object argName = args[i];
					params[i] = parameterMap.get(argName);
				}
			}
			//反射执行handler方法
			Object result = method.invoke(bean, params);
			PrintWriter writer = resp.getWriter();
			writer.println(result);
		} catch (Exception e) {}
	}

	/**
	 * handler对象
	 */
	class MethodHandler{
		private String beanName;

		private String methodName;

		private List<String> authName;

		private Object[] args;

		public String getBeanName() {
			return beanName;
		}

		public void setBeanName(String beanName) {
			this.beanName = beanName;
		}

		public String getMethodName() {
			return methodName;
		}

		public void setMethodName(String methodName) {
			this.methodName = methodName;
		}

		public Object[] getArgs() {
			return args;
		}

		public void setArgs(Object[] args) {
			this.args = args;
		}

		public List<String> getAuthName() {
			return authName;
		}

		public void setAuthName(List<String> authName) {
			this.authName = authName;
		}
	}
}
